﻿using FreeRedis;
using System.Diagnostics.CodeAnalysis;

public class Program
{
    public static void Main(string[] args)
    {
        // Redis分布式锁方案二：SETNX + value值是(系统时间+过期时间)

        // 为了解决方案一，「发生异常锁得不到释放的场景」，有小伙伴认为，可以把过期时间放到setnx的value值里面。如果加锁失败，再拿出value值校验一下即可
        // 这个方案的优点是，巧妙移除expire单独设置过期时间的操作，把「过期时间放到setnx的value值」里面来。解决了方案一发生异常，锁得不到释放的问题。
        // 但是这个方案还有别的缺点:
        //     过期时间是客户端自己生成的（System.currentTimeMillis()是当前系统的时间），必须要求分布式环境下，每个客户端的时间必须同步。
        //     如果锁过期的时候，并发多个客户端同时请求过来，都执行getSet()，最终只能有一个客户端加锁成功，但是该客户端锁的过期时间，可能被别的客户端覆盖
        //     该锁没有保存持有者的唯一标识，可能被别的客户端释放/解锁。

        Console.WriteLine("请输入秒杀商品Id：");
        var skuId = Console.ReadLine();

        Console.WriteLine("请输入秒杀商品库存:");
        var skuStockNum = int.Parse(Console.ReadLine() ?? "10");
        RedisClient redisSkuStockClient = new RedisClient("127.0.0.1:32002,defaultDatabase=1");
        var skuStockKey = $"sku:stock:{skuId}";
        redisSkuStockClient.Set<int>(skuStockKey, skuStockNum);

        Console.WriteLine("请输入秒杀数量：");
        var skuNum = int.Parse(Console.ReadLine() ?? "1");

        Console.WriteLine("请输入秒杀并发人数：");
        var userCount = int.Parse(Console.ReadLine() ?? "100");

        var redisKey = $"sale:activity:{skuId}";
        for (int i = 1; i <= userCount; i++)
        {
            var userId = i;
            Thread thread = new Thread(new ThreadStart(() =>
            {
                var expireTime = 5;
                var time = DateTime.UtcNow;
                var lockExpireTime = time.AddSeconds(expireTime).Ticks;
                RedisClient redisClient = new RedisClient("127.0.0.1:32002,defaultDatabase=1");
                if (redisClient.SetNx<long>(redisKey, lockExpireTime))
                {
                    // 如果当前锁不存在，加锁成功
                    StockHandle(redisClient, skuStockKey, skuNum, userId, redisKey);
                }
                else
                {
                    // 锁已经存在，获取锁的过期时间
                    long? _lockExpireTime = redisClient.Get<long>(redisKey);
                    if (_lockExpireTime != null && _lockExpireTime < time.Ticks)
                    {
                        // 锁已过期，获取上一个锁的过期时间，并设置现在锁的过期时间（不了解redis的getSet命令的小伙伴，可以去官网看下哈）
                        string oldValueStr = redisClient.GetSet(redisKey, lockExpireTime);
                        if (oldValueStr != null)
                        {
                            var _oldValueStr = long.Parse(oldValueStr);
                            if (_lockExpireTime == _oldValueStr)
                            {
                                // 考虑多线程并发的情况，只有一个线程的设置值和当前值相同，它才可以加锁
                                // 获取到锁
                                StockHandle(redisClient, skuStockKey, skuNum, userId, redisKey);
                            }
                            else
                            {
                                Console.WriteLine($"用户:{userId}，未获取到锁");
                            }
                        }
                        else
                        {
                            //加锁失败
                            Console.WriteLine($"用户:{userId}，未获取到锁");
                        }
                    }
                    else
                    {
                        // 加锁失败
                        Console.WriteLine($"用户:{userId}，未获取到锁");
                    }
                }
            }));
            thread.Start();
        }
    }

    public static void StockHandle(RedisClient redisClient, string skuStockKey, int skuNum, int userId, string redisKey)
    {
        var skuStock = redisClient.Get<int>(skuStockKey);
        var newSkuStock = skuStock - skuNum;
        if (newSkuStock >= 0)
        {
            Console.WriteLine($"用户:{userId}，已获取到锁，已生成订单，现库存：{skuStock},下单后库存：{newSkuStock},--------------------");
            redisClient.Set<int>(skuStockKey, newSkuStock);
            redisClient.Del(redisKey);
        }
        else
        {
            Console.WriteLine($"用户:{userId}，未生成订单，现库存：{skuStock}");
        }
    }
}